package cz.ctu.guiproject.server.dispatcher;

import cz.ctu.guiproject.server.UIServer;
import java.io.IOException;
import java.io.StringReader;
import java.util.logging.Logger;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import cz.ctu.guiproject.server.manager.ConnectionManager;
import cz.ctu.guiproject.server.manager.UIManager;
import cz.ctu.guiproject.server.messaging.ForceUIUpdate;
import cz.ctu.guiproject.server.networking.ServerNetworkAgent;
import cz.ctu.guiproject.server.networking.ServerNetworkAgentImpl;
import cz.ctu.guiproject.server.networking.ServerNetworkObserver;
import cz.ctu.guiproject.server.resources.Constants;
import cz.ctu.guiproject.server.shared.Event;
import guiproject.server.view.Updater;
import java.io.StringWriter;
import java.util.logging.Level;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

public class Dispatcher implements ServerNetworkObserver, Updater {

    private static final Logger logger = Logger.getLogger(Dispatcher.class.getName());
    private static Dispatcher instance;
    private ServerNetworkAgent networkAgent;
    private ConnectionManager connectionManager;
    private UIManager uiManager;
    private UIServer uiServer;
    
    private Dispatcher() {
    }
    
    public static Dispatcher getInstance() {
        if(instance == null) {
            instance = new Dispatcher();
        }
        return instance;
    }
    
    public void init(int port, UIServer uiServer) {
        this.uiServer = uiServer;
        networkAgent = new ServerNetworkAgentImpl(port);
        networkAgent.registerObserver(this);
        connectionManager = new ConnectionManager(this);
        uiManager = new UIManager(this);
    }
    
    @Override
    public void update(Object message) {
        // TODO get rid of [no_of_session]
        String xml = message.toString();
        xml = xml.substring(xml.indexOf(']') + 1);
        // parse XML and get root tag name
        final SAXParserFactory factory = SAXParserFactory.newInstance();
        try {
            final SAXParser saxParser = factory.newSAXParser();
            final DefaultHandler handler = new TagResolver(message.toString());
            saxParser.parse(new InputSource(new StringReader(xml)), handler);
        } catch (ParserConfigurationException | SAXException | IOException e) {
            logger.severe(e.getMessage());
        }
    }

    private class TagResolver extends DefaultHandler {

        private final String xml;

        TagResolver(String xml) {
            this.xml = xml;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes)
                throws SAXException {
            switch (qName) {
                case Constants.CLIENT_INIT_REQUEST:
                    logger.info(Constants.CLIENT_INIT_REQUEST);
                    connectionManager.initClient(xml);
                    throw new SAXException(Constants.SAX_PREMATURE_END);
                case Constants.CLIENT_DISCONNECT_REQUEST:
                    logger.info(Constants.CLIENT_DISCONNECT_REQUEST);
                    connectionManager.disconnectClient(xml);
                    throw new SAXException(Constants.SAX_PREMATURE_END);
                case Constants.CLIENT_UI_REQUEST:
                    logger.info(Constants.CLIENT_UI_REQUEST);
                    uiManager.initUI(xml);
//                    // TODO just to debug!!!
//                    try {
//                        Thread.sleep(5000);
//                    } catch (InterruptedException ex) {
//                        Logger.getLogger(Dispatcher.class.getName()).log(Level.SEVERE, null, ex);
//                    }
//                    forceUIUpdate("InnerButton", "android:text", "ChangedButton!");
                    throw new SAXException(Constants.SAX_PREMATURE_END);
                case Constants.CLIENT_PROPAGATE_UI_UPDATE:
                    logger.info(Constants.CLIENT_PROPAGATE_UI_UPDATE);
                    uiManager.propagateUIUpdate(xml);
                    throw new SAXException(Constants.SAX_PREMATURE_END);
                case Constants.CLIENT_EVENT_OCCURED:
                    logger.info(Constants.CLIENT_EVENT_OCCURED);
                    uiManager.eventOccured(xml);
                    throw new SAXException(Constants.SAX_PREMATURE_END);
                default:
                    logger.log(Level.INFO, Constants.UNKNOWN_TAG, qName);
            }
        }
    }

    @Override
    public void forceUIUpdate(String viewId, String attrName, String attrValue) {
        final ForceUIUpdate forceUIUpdate = new ForceUIUpdate();
        forceUIUpdate.setViewId(viewId);
        forceUIUpdate.setAttrName(attrName);
        forceUIUpdate.setAttrValue(attrValue);
        final String forceUIUpdateXml = encodeForceUIUpdate(forceUIUpdate);
        networkAgent.broadcast(forceUIUpdateXml);
        // TODO update layout on server side as well
    }

    private String encodeForceUIUpdate(ForceUIUpdate forceUIUpdate) {
        final StringWriter writer = new StringWriter();
        try {
            final XMLStreamWriter out = XMLOutputFactory.newInstance()
                    .createXMLStreamWriter(writer);

            out.writeStartDocument(Constants.CHARSET_UTF_8, Constants.VERSION_1_0);
            out.writeStartElement(Constants.CLIENT_FORCE_UI_UPDATE);

            out.writeStartElement(Constants.FORCE_VIEW_ID);
            out.writeCharacters(forceUIUpdate.getViewId());
            out.writeEndElement();

            out.writeStartElement(Constants.FORCE_ATTRIBUTE_NAME);
            out.writeCharacters(forceUIUpdate.getAttrName());
            out.writeEndElement();

            out.writeStartElement(Constants.FORCE_ATTRIBUTE_VALUE);
            out.writeCharacters(forceUIUpdate.getAttrValue());
            out.writeEndElement();

            out.writeEndElement();
            out.writeEndDocument();
            out.close();

        } catch (XMLStreamException | FactoryConfigurationError e) {
            logger.severe(e.getMessage());
        }
        final StringBuffer sb = writer.getBuffer();

        return sb.toString();
    }

    public void send(int networkId, Object message) {
        networkAgent.send(networkId, message);
    }

    public void eventOccured(Event event) {
        uiServer.eventOccured(event);
    }
}
